/****************************************************************************
 * Copyright (C) 2012 Dj_Skual
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ByteArray.cpp
 * for SaveGame Manager GX 2012
 ****************************************************************************/
#include <assert.h>
#include <math.h>

#include "ByteArray.h"

ByteArray::ByteArray()
{
	Datas = 0;
	Size = 0;
	streamOffset = 0;
}

ByteArray::ByteArray(int size)
	: Datas(0)
	, Size(size)
	, streamOffset(0)
{
	Datas = new u8[Size+1];
	for(int i = 0; i < Size; ++i)
		(*this)[i] = 0;
	Datas[Size] = '\0';
}

ByteArray::ByteArray(const ByteArray &array)
	: Datas(0)
	, Size(0)
	, streamOffset(0)
{
	(*this) = array;
}

ByteArray::ByteArray(u8 * data, int size)
	: Datas(0)
	, Size(size)
	, streamOffset(0)
{
	Datas = new u8[Size+1];
	for(int i = 0; i < Size; ++i)
		(*this)[i] = data[i];
	Datas[Size] = '\0';
}

ByteArray::ByteArray(std::string str)
	: Datas(0)
	, Size(str.size())
	, streamOffset(0)
{
	Datas = new u8[Size+1];
	for(int i = 0; i < Size; ++i)
		(*this)[i] = str[i];
	Datas[Size] = '\0';
}

ByteArray::ByteArray(int size, char ch)
	: Datas(0)
	, Size(size)
	, streamOffset(0)
{
	Datas = new u8[Size+1];
	for(int i = 0; i < Size; ++i)
		(*this)[i] = ch;
	Datas[Size] = '\0';
}

ByteArray::~ByteArray()
{
	delete[] Datas;
}

u8& ByteArray::operator[](int index)
{
	assert(index >=0 && index < Size);
	return Datas[index];
}

ByteArray& ByteArray::operator=(const ByteArray &array)
{
	if(this == &array)
		return (*this);
	
	clear();
	Size = array.Size;
	Datas = new u8[Size+1];
	for (int i = 0; i < Size; ++i)
		(*this)[i] = array.Datas[i];
	Datas[Size] = '\0';
	
	return (*this);
}

ByteArray& ByteArray::operator+=(const ByteArray &array)
{
	int oldSize = Size;
	resize(oldSize + array.Size);
	
	for (int i = 0; i < (Size-oldSize); ++i)
		(*this)[i+oldSize] = array.Datas[i];
	
	return (*this);
}

const ByteArray ByteArray::operator+(const ByteArray &array) const
{
    return ByteArray(*this) += array;
}

bool ByteArray::operator==(const ByteArray &array)
{
    if(array.Size != Size)
		return false;
	
	for (int i = 0; i < Size; ++i)
	{
		if((*this)[i] != array.Datas[i])
			return false;
	}
	
	return true;
}

bool ByteArray::operator!=(const ByteArray &array)
{
    return !((*this) == array);
}

ByteArray::operator const char*()
{
	return (const char*)Datas;
}

ByteArray::operator void*()
{
	return (void*)Datas;
}

ByteArray& ByteArray::operator>>(u8 &val)
{
	if(atEnd())
		return (*this);
	
	val = (*this)[streamOffset];
	++streamOffset;
	
	return (*this);
}

ByteArray& ByteArray::operator>>(u16 &val)
{
	if((streamOffset + 1) >= Size)
		return (*this);
	
	val = (*this).be16(streamOffset);
	streamOffset += 2;
	
	return (*this);
}

ByteArray& ByteArray::operator>>(u32 &val)
{
	if((streamOffset + 3) >= Size)
		return (*this);
	
	val = (*this).be32(streamOffset);
	streamOffset += 4;
	
	return (*this);
}

ByteArray& ByteArray::operator>>(u64 &val)
{
	if((streamOffset + 7) >= Size)
		return (*this);
	
	val = (*this).be64(streamOffset);
	streamOffset += 8;
	
	return (*this);
}

ByteArray& ByteArray::operator>>(char &ch)
{
	if(atEnd())
		return (*this);
	
	ch = (*this)[streamOffset];
	++streamOffset;
	
	return (*this);
}

ByteArray& ByteArray::operator>>(std::string &str)
{
	for(int i = 0; !atEnd() && (i < (int)str.size()); ++i)
	{
		str[i] = (*this)[streamOffset];
		++streamOffset;
	}
	
	return (*this);
}

ByteArray& ByteArray::operator>>(ByteArray &array)
{
	for(int i = 0; !atEnd() && (i < array.Size); ++i)
	{
		array.Datas[i] = (*this)[streamOffset];
		++streamOffset;
	}
	
	return (*this);
}

ByteArray& ByteArray::operator<<(const u8 &val)
{
	if(atEnd())
		return (*this);
	
	(*this)[streamOffset] = val;
	++streamOffset;
	
	return (*this);
}

ByteArray& ByteArray::operator<<(const u16 &val)
{
	if((streamOffset + 1) >= Size)
		return (*this);
	
	(*this).wbe16(streamOffset, val);
	streamOffset += 2;
	
	return (*this);
}

ByteArray& ByteArray::operator<<(const u32 &val)
{
	if((streamOffset + 3) >= Size)
		return (*this);
	
	(*this).wbe32(streamOffset, val);
	streamOffset += 4;
	
	return (*this);
}

ByteArray& ByteArray::operator<<(const u64 &val)
{
	if((streamOffset + 7) >= Size)
		return (*this);
	
	(*this).wbe64(streamOffset, val);
	streamOffset += 8;
	
	return (*this);
}

ByteArray& ByteArray::operator<<(const char ch)
{
	if(atEnd())
		return (*this);
	
	(*this)[streamOffset] = ch;
	++streamOffset;
	
	return (*this);
}

ByteArray& ByteArray::operator<<(const std::string str)
{
	for(int i = 0; !atEnd() && (i < (int)str.size()); ++i)
	{
		(*this)[streamOffset] = str[i];
		++streamOffset;
	}
	
	return (*this);
}

ByteArray& ByteArray::operator<<(const ByteArray &array)
{
	for(int i = 0; !atEnd() && i < array.Size; ++i)
	{
		(*this)[streamOffset] = array.Datas[i];
		++streamOffset;
	}
	
	return (*this);
}

bool ByteArray::empty()
{
	for(int i = 0; i < Size; ++i)
	{
		if((*this)[i] != 0)
			return false;
	}
	
	return true;
}

void ByteArray::clear()
{
	delete[] Datas;
	Datas = 0;
	Size = 0;
	streamOffset = 0;
}

void ByteArray::realloc(int size)
{
	clear();
	
	if(size <= 0)
		return;
	
	Size = size;
	Datas = new u8[size+1];
	for(int i = 0; i < Size; ++i)
		(*this)[i] = 0;
	Datas[size] = '\0';
}

void ByteArray::resize(int size)
{
	if(size <= 0)
	{
		clear();
		return;
	}
	
	u8 *data = new u8[size+1];
	for(int i = 0; i < size; ++i)
		data[i] = 0;
	data[size] = '\0';
	
	if(Size > 0)
	{
		int nElementsToCopy = (size > Size) ? Size : size;
		
		for (int index = 0; index < nElementsToCopy; index++)
			data[index] = Datas[index];
	}
	
	delete[] Datas;
	
	Datas = data;
	Size = size;
	streamOffset = 0;
}

void ByteArray::chop(int n)
{
	resize(Size-n);
}

const ByteArray ByteArray::left(int size)
{
	if((Size <= 0) || (size <= 0))
		return ByteArray();
	
	ByteArray result = (*this);
	result.resize(size);
	
	return result;
}

const ByteArray ByteArray::mid(int index, int size)
{
	assert(index >=0 && index < Size);
	
	if((Size <= 0) || !size || (size < -1))
		return ByteArray();
	else if(((index + size) >= Size) || (size == -1))
		size = Size - index;
	
	ByteArray result(size);
	for (int i = 0; i < size; ++i)
		result[i] = (*this)[index+i];
	
	return result;
}

const ByteArray ByteArray::right(int size)
{
	if((Size <= 0) || (size <= 0))
		return ByteArray();
	else if(size >= Size)
		size = Size;
	
	ByteArray result(size);
	int index = Size - size;
	for (int i = 0; i < size; ++i)
		result[i] = (*this)[index+i];
	
	return result;
}

void ByteArray::append(const ByteArray &array)
{
	(*this) += array;
}

void ByteArray::insert(int index, const ByteArray &array)
{
	assert(index >= 0);
	
	if(index >= Size)
		resize(index+1);
	
	ByteArray newArray = left(index) + array + right(Size-index);
	clear();
	(*this) = newArray;
}

void ByteArray::insert(int index, u8 * data, int size)
{
	ByteArray array(data, size);
	insert(index, array);
}

void ByteArray::insert(int index, char ch)
{
	ByteArray array(1, ch);
	insert(index, array);
}

ByteArray& ByteArray::remove(int index, int size)
{
	if((index < 0) || (index >= Size))
		return (*this);
	
	ByteArray newArray = left(index) + right(Size-(index+size));
	clear();
	(*this) = newArray;
	
	return (*this);
}

int ByteArray::seek(int offset, int origin)
{
	if(!Size)
		return 1;
	
	if(origin == SEEK_SET)
	{
		if(offset < Size)
		{
			streamOffset = offset;
			return 0;
		}
	}
	else if(origin == SEEK_CUR)
	{
		if((offset + streamOffset) < Size)
		{
			streamOffset += offset;
			return 0;
		}
	}
	else if((origin == SEEK_END) && (offset < 0))
	{
		if((offset + streamOffset) >= 0)
		{
			streamOffset += offset;
			return 0;
		}
	}
	
	return 1;
}

int ByteArray::skipRawData(int len)
{
	int count = (((streamOffset + len) < Size) ? len : (Size - streamOffset - 1));
	
	streamOffset += count;
	
	return count;
}

bool ByteArray::atEnd()
{
	return (streamOffset >= Size);
}

u16 ByteArray::be16(int index)
{
	return ((*this)[index] << 8) | (*this)[index+1];
}

u32 ByteArray::be32(int index)
{
	return ((u32)be16(index) << 16) | be16(index + 2);
}

u64 ByteArray::be64(int index)
{
	return ((u64)be32(index) << 32) | be32(index + 4);
}

void ByteArray::wbe16(int index, u16 data)
{
	(*this)[index] = data >> 8;
	(*this)[index+1] = data;
}

void ByteArray::wbe32(int index, u32 data)
{
	wbe16(index, data >> 16);
	wbe16(index + 2, data);
}

void ByteArray::wbe64(int index, u64 data)
{
	wbe32(index, data >> 32);
	wbe32(index + 4, data);
}

ByteArray PaddedByteArray(const ByteArray &array, int size)
{
	ByteArray result = array;
	
	if(!size || result.empty())
		return result;
	
	int new_size = (int)ceil((float)result.size()/(float)size)*size;
	
	result.resize(new_size);
	
	return result;
}
